/*
 * Copyright (C) 2008-2010 Oliver Horn.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.funcj.data;


import java.io.Serializable;
import java.util.Comparator;
import java.util.Iterator;

import javax.annotation.Nullable;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
import com.googlecode.funcj.data.Maybe.Just;
import com.googlecode.funcj.data.Maybe.Nothing;

/**
 * A collection of utility methods that deal with {@code Maybe} objects.
 *
 * @author Oliver Horn
 */
public final class Maybes {

	private Maybes() {
	}

	private final static Nothing<?> NOTHING = new Nothing<Object>();

	/**
	 * Returns an empty {@code Maybe} object.
	 * @param <T>
	 * @return {@code Nothing}
	 */
	@SuppressWarnings("unchecked")
	public static <T> Nothing<T> nothing() {
		return (Nothing<T>) NOTHING;
	}

	/**
	 * Creates a {@code Maybe} object which encapsulates the given value.
	 * @param <T>
	 * @param value
	 * @return {@code Just}
	 */
	public static <T> Just<T> just(@Nullable T value) {
		return new Maybe.Just<T>(value);
	}

	public static <T> Maybe<T> justIf(@Nullable T value, Predicate<? super T> predicate) {
		return predicate.apply(value) ? Maybes.just(value) : Maybes.<T>nothing();
	}

	public static <T> Maybe<T> justIfNotNull(@Nullable T value) {
		return justIf(value, Predicates.notNull());
	}

	public static <T> Maybe<T> maybeFromIterable(Iterable<T> iterable) {
		Iterator<T> iterator = iterable.iterator();
		if (iterator.hasNext()) {
			return just(iterator.next());
		} else {
			return nothing();
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> Function<T, Maybe<T>> justFunction() {
		return (Function<T, Maybe<T>>) JustFunction.INSTANCE;
	}

	public static <T> Function<T, Maybe<T>> justIfFunction(Predicate<? super T> predicate) {
		return new JustIfFunction<T>(predicate);
	}

	@SuppressWarnings("rawtypes")
	public static <T extends Comparable> Ordering<Maybe<T>> maybeOrdering(boolean nothingFirst) {
		return maybeOrdering(Ordering.<T>natural(), nothingFirst);
	}

	public static <T> Ordering<Maybe<T>> maybeOrdering(Comparator<T> justComparator, boolean nothingFirst) {
		return new MaybeOrdering<T>(justComparator, nothingFirst ? -1 : 1);
	}

	@SuppressWarnings("unchecked")
	public static <T> Function<Maybe<T>, T> fromMaybeFunction() {
		return (Function<Maybe<T>, T>) FromMaybeFunction.INSTANCE;
	}

	@SuppressWarnings("unchecked")
	public static <T> Predicate<Maybe<T>> isJustPredicate() {
		return IsJustPredicate.INSTANCE;
	}

	@SuppressWarnings("unchecked")
	public static <T, V> Iterable<V> transformMaybe(Iterable<T> input, Function<? super T,Maybe<? extends V>> function) {
		return Iterables.transform(
				Iterables.filter(input, IsJustPredicate.INSTANCE),
				Functions.compose(function, FromMaybeFunction.INSTANCE));
	}

	@SuppressWarnings("rawtypes")
	private enum JustFunction implements Function {
		INSTANCE;

		public Object apply(Object o) {
			return new Maybe.Just<Object>(o);
		}

		@Override
		public String toString() {
			return "just";
		}
	}

	private static class JustIfFunction<T> implements Function<T, Maybe<T>>, Serializable {

		private static final long serialVersionUID = 0;

		private final Predicate<? super T> predicate;

		public JustIfFunction(Predicate<? super T> predicate) {
			this.predicate = predicate;
		}

		@SuppressWarnings("unchecked")
		public Maybe<T> apply(T o) {
			return predicate.apply(o) ? new Maybe.Just<T>(o) : (Maybe<T>) NOTHING;
		}

		@Override
		public boolean equals(Object obj) {
			if (obj instanceof JustIfFunction) {
				return predicate.equals(((JustIfFunction<?>) obj).predicate);
			}
			return false;
		}

		@Override
		public int hashCode() {
			return predicate.hashCode();
		}

		@Override
		public String toString() {
			return "justIf(" + predicate + ")";
		}
	}

	@SuppressWarnings("rawtypes")
	private enum FromMaybeFunction implements Function {
		INSTANCE;

		public Object apply(Object o) {
			return ((Maybe) o).get();
		}

		@Override
		public String toString() {
			return "fromMaybe";
		}
	}

	@SuppressWarnings("rawtypes")
	private enum IsJustPredicate implements Predicate {
		INSTANCE;

		public boolean apply(Object o) {
			return ((Maybe) o).isJust();
		}

		@Override
		public String toString() {
			return "isJust";
		}
	}

	private static class MaybeOrdering<T> extends Ordering<Maybe<T>> {

		private static final long serialVersionUID = 0;

		private final Comparator<T> justComparator;
		private final int nothingToJust;

		public MaybeOrdering(Comparator<T> justComparator, int nothingToJust) {
			this.justComparator = justComparator;
			this.nothingToJust = nothingToJust;
		}

		public int compare(Maybe<T> maybe1, Maybe<T> maybe2) {
			if (maybe1.isNothing()) {
				return maybe2.isNothing() ? 0 : nothingToJust;
			} else {
				return maybe2.isJust() ? justComparator.compare(maybe1.get(), maybe2.get()) : -nothingToJust;
			}
		}
	}
}
